package com.yxx.service.Impl;

import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yxx.entity.BlogFile;
import com.yxx.entityModel.vo.Result.PageResult;
import com.yxx.entityModel.vo.query.FileQuery;
import com.yxx.entityModel.vo.request.FolderRequest;
import com.yxx.entityModel.vo.response.FileResponse;
import com.yxx.exception.ServiceException;
import com.yxx.mapper.BlogFileMapper;
import com.yxx.service.BlogFileService;
import com.yxx.strategy.context.UploadStrategyContext;
import com.yxx.utils.FileUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static com.yxx.constant.CommonConstant.FALSE;
import static com.yxx.constant.CommonConstant.TRUE;

/**
 * @author yuxiangxun
 * @date 2025/2/1
 * @apiNote
 */
@Service
@Slf4j
public class BlogFileServiceImpl extends ServiceImpl<BlogFileMapper, BlogFile> implements BlogFileService {


    /**
     * 本地路径
     */
    @Value("${upload.local.path}")
    private String localPath;

    @Resource
    private BlogFileMapper blogFileMapper;

    @Resource
    private UploadStrategyContext uploadStrategyContext;

    @Resource
    private HttpServletResponse httpServletResponse;

    @Override
    public void saveBlogFile(MultipartFile file, String url, String filePath) {
        try{
            // 获取文件 md5 值
            String md5 = FileUtils.getMd5(file.getInputStream());
            // 获取文件扩展名
            String extensionName = FileUtils.getExtension(file);
            // 根据文件md5和文件路径查询文件id，用来判断文件是否存在
            BlogFile existBlogFile = blogFileMapper.selectOne(new LambdaQueryWrapper<BlogFile>()
                    .select(BlogFile::getId)
                    .eq(BlogFile::getFileName, md5)
                    .eq(BlogFile::getFilePath, filePath)
            );
            // 若文件不存在直接返回
            if(Objects.nonNull(existBlogFile)){
                return;
            }
            // 若文件存在，保存文件信息到数据库
            BlogFile blogFile = BlogFile.builder()
                                .fileUrl(url)
                                .fileName(md5)
                                .extendName(extensionName)
                                .filePath(filePath)
                                .fileSize((int) file.getSize())
                                .isDir(FALSE)
                                .build();
            blogFileMapper.insert(blogFile);
        }catch (IOException e){
            log.error("SaveBlogFile is error: {}",e.getMessage());
        }
    }

    /**
     * 文件列表
     * @param fileQuery
     * @return
     */
    @Override
    public PageResult<FileResponse> listFileVOList(FileQuery fileQuery) {
        // 查询文件数量
        Long count = blogFileMapper.selectCount(new LambdaQueryWrapper<BlogFile>()
                .eq(BlogFile::getFilePath, fileQuery.getFilePath()));
        if (count == 0) {
            return new PageResult<>();
        }
        // 查询文件列表
        List<FileResponse> fileRespList = blogFileMapper.selectFileVOList(fileQuery);
        return new PageResult<>(fileRespList, count);
    }

    /**
     * 上传文件
     * @param file
     * @param filePath
     */
    @Override
    public void uploadFile(MultipartFile file, String filePath) {
        String uploadPath = "/".equals(filePath) ? filePath : filePath + "/";
        // 上传文件
        String url = uploadStrategyContext.executeUploadStrategy(file, uploadPath);
        // 将相关数据保存到数据库中的方法，还有一些参数需要处理才能保存到数据库
        saveBlogFile(file,url,filePath);
    }

    /**
     * 创建目录
     * @param folderRequest
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createFolder(FolderRequest folderRequest) {
        String fileName = folderRequest.getFileName();
        String filePath = folderRequest.getFilePath();
        // 判断目录是否存在
        BlogFile existBlogFile = blogFileMapper.selectOne(new LambdaQueryWrapper<BlogFile>()
                .select(BlogFile::getId)
                .eq(BlogFile::getFileName, fileName)
                .eq(BlogFile::getFilePath, filePath)
        );
        // 如果目录存在，抛出异常
        Assert.isNull(existBlogFile, fileName + "-这个目录已存在");
        // 创建目录
        File directory = new File(localPath + filePath + "/" + fileName);
        if(FileUtils.mkdir(directory)){
            BlogFile newBlogFile = BlogFile.builder()
                    .fileName(fileName)
                    .filePath(filePath)
                    .isDir(TRUE)
                    .build();
            blogFileMapper.insert(newBlogFile);
        }else{
            throw new ServiceException("创建目录失败");
        }
    }

    /**
     * 删除文件
     * @param fileIdList
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteFile(List<Integer> fileIdList) {
        // 根据文件id集合查询文件信息
        List<BlogFile> blogFiles = blogFileMapper.selectList(new LambdaQueryWrapper<BlogFile>()
                .select(BlogFile::getFileName, BlogFile::getFilePath, BlogFile::getIsDir, BlogFile::getExtendName)
                .in(BlogFile::getId, fileIdList)
        );
        // 删除数据库中相关的文件信息
        blogFileMapper.deleteBatchIds(fileIdList);
        // 删除留在本地磁盘中的文件，删除目录和文件，要删除他们哦
        blogFiles.forEach(blogFile -> {
            File file;
            String fileName = localPath + blogFile.getFilePath() + "/" + blogFile.getFileName();
            // 该文件类型是目录
            if(blogFile.getIsDir().equals(TRUE)){
                // 删除数据库中的剩余的子文件吧
                String filePath = blogFile.getFilePath() + blogFile.getFileName();
                blogFileMapper.delete(new LambdaQueryWrapper<BlogFile>()
                        .eq(BlogFile::getFilePath,filePath)
                );
                // 删除目录
                file = new File(fileName);
                // 如果本地磁盘有对应的目录存在，删除掉
                if(file.exists()){
                    FileUtils.deleteFile(file);
                }
            }else {
                // 不是目录，删除文件
                file = new File(fileName + "." + blogFile.getExtendName());
                if(file.exists()){
                    // 如果本地磁盘有对应的文件存在，删除掉
                    file.delete();
                }
            }
        });
    }

    /**
     * 下载文件
     * @param fileId
     */
    @Override
    public void downloadFile(Integer fileId) {
        // 查询文件信息
        BlogFile blogFile = blogFileMapper.selectOne(new LambdaQueryWrapper<BlogFile>()
                .select(BlogFile::getFilePath,
                        BlogFile::getFileName,
                        BlogFile::getExtendName,
                        BlogFile::getIsDir)
                .eq(BlogFile::getId, fileId));
        Assert.notNull(blogFile, "文件不存在");
        // 拼接文件路径
        String filePath = localPath + blogFile.getFilePath() + "/";
        // 要下载的不是目录而是文件
        if(blogFile.getIsDir().equals(FALSE)){
            // 拼接文件名
            String fileName = blogFile.getFileName() + "." + blogFile.getExtendName();
            // 重载 下载文件方法
            downloadFile(filePath, fileName);
        }else {
            // 下载的是目录则压缩下载
            // 目录的绝对路径
            String fileName = filePath + blogFile.getFileName();
            File src = new File(fileName);
            File dest = new File(fileName + ".zip");
            try{
                ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(dest));
                // 压缩文件
                toZip(src, zipOutputStream, src.getName());
                zipOutputStream.close();
                // 下载压缩包
                downloadFile(filePath, blogFile.getFileName() + ".zip");
            }catch (IOException e){
                log.error("downloadFile fail, {}", e.getMessage());
            }finally {
                if(dest.exists()){
                    dest.delete();
                }
            }
        }
    }

    /**
     * 下载文件
     * @param filePath 文件路径
     * @param fileName 文件名
     */
    private void downloadFile(String filePath, String fileName) {
        FileInputStream fileInputStream = null;
        ServletOutputStream outputStream = null;
        try {
            // 设置文件名
            httpServletResponse.addHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
            fileInputStream = new FileInputStream(filePath + fileName);
            outputStream = httpServletResponse.getOutputStream();
            IOUtils.copyLarge(fileInputStream, outputStream);
        } catch (IOException e) {
            throw new ServiceException("文件下载失败");
        } finally {
            IOUtils.closeQuietly(fileInputStream);
            IOUtils.closeQuietly(outputStream);
        }
    }

    /**
     * 压缩文件夹
     * @param src             源文件
     * @param zipOutputStream 压缩输出流
     * @param name            文件名
     * @throws IOException IO异常
     */
    private static void toZip(File src, ZipOutputStream zipOutputStream, String name) throws IOException {
        for (File file : Objects.requireNonNull(src.listFiles())) {
            if (file.isFile()) {
                // 判断文件，变成ZipEntry对象，放入到压缩包中
                ZipEntry zipEntry = new ZipEntry(name + "/" + file.getName());
                // 读取文件中的数据，写到压缩包
                zipOutputStream.putNextEntry(zipEntry);
                FileInputStream fileInputStream = new FileInputStream(file);
                int b;
                while ((b = fileInputStream.read()) != -1) {
                    zipOutputStream.write(b);
                }
                fileInputStream.close();
                zipOutputStream.closeEntry();
            } else {
                toZip(file, zipOutputStream, name + "/" + file.getName());
            }
        }
    }


}
